Skip to content

功能包整合与 GitHub 管理

0. 为什么要整合

前两篇笔记完成了导航仿真的搭建,但存在一个问题:部分配置需要直接修改系统文件(/opt/ros/humble/...),这导致:

•       换一台机器就要重新手动修改系统文件 •       无法通过 git clone 直接复现环境 •       代码散落在系统各处,不便管理和分享

正确做法是将所有文件收进一个标准 ROS2 功能包,自己的 launch 文件加载自己包里的资源,完全不依赖修改系统文件。

1. 目标目录结构

整合后的功能包结构如下:

bash
campus_nav/
├── config/
│   ├── ekf.yaml              # EKF 融合参数
│   ├── navsat.yaml           # GPS 坐标转换参数
│   └── nav2_params.yaml      # Nav2 导航参数
├── launch/
│   ├── bringup.launch.py     # 一键启动(推荐使用)
│   ├── sim.launch.py         # 仅启动仿真环境
│   ├── localization.launch.py
│   └── nav2.launch.py
├── models/
│   └── turtlebot3_waffle/
│       ├── model.sdf         # 加入 GPS 传感器的 SDF
│       └── model.config
├── urdf/
│   ├── turtlebot3_waffle.urdf  # 加入 GPS link 的 URDF
│   └── common_properties.urdf
├── worlds/
│   └── turtlebot3_world.world  # 设置天津坐标参考点
├── CMakeLists.txt
├── package.xml
├── .gitignore
└── README.md

NOTE

⚠ 关键原则:所有曾经修改过的系统文件(URDF、SDF、world)都复制到功能包里统一管理,不再依赖系统路径。

2. package.xml

package.xml 声明包的基本信息和依赖关系,是 ROS2 功能包的必要文件:

xml
<?xml version="1.0"?>
<package format="3">
  <name>campus_nav</name>
  <version>0.1.0</version>
  <description>Campus autonomous navigation based on RTK + Nav2</description>
  <maintainer email="your@email.com">your_name</maintainer>
  <license>MIT</license>
  <buildtool_depend>ament_cmake</buildtool_depend>
  <!-- 运行时依赖 -->
  <exec_depend>robot_localization</exec_depend>
  <exec_depend>nav2_bringup</exec_depend>
  <exec_depend>turtlebot3_gazebo</exec_depend>
  <exec_depend>gazebo_ros</exec_depend>
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

3. CMakeLists.txt

CMakeLists.txt 告诉 colcon 编译器如何处理这个包。对于纯配置包(无 C++ 代码),核心是把所有目录安装到 share 路径下:

xml
cmake_minimum_required(VERSION 3.8)
project(campus_nav)
find_package(ament_cmake REQUIRED)

把所有资源目录安装到 share/campus_nav/

makefile
install(DIRECTORY
  config
  launch
  urdf
  worlds
  models
  DESTINATION share/${PROJECT_NAME}
)
ament_package()

说明:install(DIRECTORY ...) 执行后,colcon build 会把这些目录复制到 install/campus_nav/share/campus_nav/,launch 文件里用 get_package_share_directory('campus_nav') 就能找到它们。

4. bringup.launch.py —— 一键启动

bringup.launch.py 是整个系统的总入口,按顺序启动所有节点。使用 TimerAction 控制启动时序,避免节点依赖问题:

python
import os
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, TimerAction, SetEnvironmentVariable
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
    pkg         = get_package_share_directory('campus_nav')
    pkg_gz_ros  = get_package_share_directory('gazebo_ros')
    pkg_tb3_gz  = get_package_share_directory('turtlebot3_gazebo')
    pkg_nav2    = get_package_share_directory('nav2_bringup')
    urdf_path   = os.path.join(pkg, 'urdf',   'turtlebot3_waffle.urdf')
    world_path  = os.path.join(pkg, 'worlds', 'turtlebot3_world.world')
    ekf_yaml    = os.path.join(pkg, 'config', 'ekf.yaml')
    navsat_yaml = os.path.join(pkg, 'config', 'navsat.yaml')
    nav2_yaml   = os.path.join(pkg, 'config', 'nav2_params.yaml')
    with open(urdf_path, 'r') as f:
        robot_desc = f.read()
    return LaunchDescription([
        # ── 1. 仿真环境(立即启动)──────────────────
        SetEnvironmentVariable(
            name='GAZEBO_MODEL_PATH',
            value=os.path.join(pkg, 'models') + ':' +
                  os.path.join(pkg_tb3_gz, 'models')
        ),
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(pkg_gz_ros, 'launch', 'gzserver.launch.py')
            ),
            launch_arguments={'world': world_path}.items()
        ),
        IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
                os.path.join(pkg_gz_ros, 'launch', 'gzclient.launch.py')
            )
        ),
        Node(
            package='robot_state_publisher',
            executable='robot_state_publisher',
            parameters=[{'robot_description': robot_desc, 'use_sim_time': True}]
        ),
        Node(
            package='gazebo_ros',
            executable='spawn_entity.py',
            arguments=[
                '-entity', 'waffle',
                '-file', os.path.join(pkg, 'models', 'turtlebot3_waffle', 'model.sdf'),
                '-x', '-2.0', '-y', '-0.5', '-z', '0.01'
            ],
            output='screen'
        ),
        # ── 2. EKF 定位(延迟 3s 等 Gazebo 就绪)────
        TimerAction(period=3.0, actions=[
            Node(
                package='robot_localization',
                executable='ekf_node',
                name='ekf_filter_node',
                parameters=[ekf_yaml, {'use_sim_time': True}],
                output='screen'
            ),
        ]),
        # ── 3. navsat_transform(延迟 6s 等 EKF 就绪)
        TimerAction(period=6.0, actions=[
            Node(
                package='robot_localization',
                executable='navsat_transform_node',
                name='navsat_transform',
                parameters=[navsat_yaml, {'use_sim_time': True}],
                output='screen'
            ),
        ]),
        # ── 4. Nav2(延迟 8s)────────────────────────
        TimerAction(period=8.0, actions=[
            IncludeLaunchDescription(
                PythonLaunchDescriptionSource(
                    os.path.join(pkg_nav2, 'launch', 'navigation_launch.py')
                ),
                launch_arguments={
                    'use_sim_time': 'true',
                    'params_file': nav2_yaml,
                }.items()
            ),
        ]),
        # ── 5. Rviz(延迟 10s)───────────────────────
        TimerAction(period=10.0, actions=[
            IncludeLaunchDescription(
                PythonLaunchDescriptionSource(
                    os.path.join(pkg_nav2, 'launch', 'rviz_launch.py')
                ),
                launch_arguments={'use_sim_time': 'true'}.items()
            ),
        ]),
    ])

说明:TimerAction 的延迟时间是经验值。Gazebo 启动最慢需要约 3 秒,EKF 需要等 Gazebo 的话题就绪,navsat_transform 需要等 EKF 输出 /odometry/filtered,Nav2 最后启动。

5. 编译与验证

bash
cd ~/carplaning/nav_ws
colcon build --packages-select campus_nav
source install/setup.bash

设置机器人型号

bash
export TURTLEBOT3_MODEL=waffle

一键启动

bash
ros2 launch campus_nav bringup.launch.py

约 10 秒后 Rviz 打开,看到激光扫描红点出现后,点击顶部工具栏 Nav2 Goal 设置目标点,小车开始自动导航。

6. Git 分支规划

本项目分仿真和实物两个阶段,用两个分支管理:

simulation  ←  仿真版本(当前,长期保留)

hardware    ←  实物版本(后续从 simulation 切出来修改)

选择分支而非两个仓库的原因:两套代码共享同一套导航逻辑(Nav2配置、EKF配置),只有传感器驱动层不同。用分支可以清楚追踪从仿真到实物改了哪些文件。

6.1 初始化仓库并推送

bash
cd ~/carplaning/nav_ws/src/campus_nav

git init

git add .

git commit -m "feat: 仿真阶段完成 - Turtlebot3 + EKF + Nav2 无图导航"

将当前分支命名为 simulation

bash
git branch -M simulation

关联远程仓库并推送

bash
git remote add origin https://github.com/你的用户名/仓库名.git

git push -u origin simulation

6.2 后续创建实物分支

当开始实物阶段时,从 simulation 分支切出 hardware 分支:

bash
# 基于仿真分支创建实物分支

git checkout -b hardware

# 在 hardware 分支上修改(替换传感器驱动等)

# ...

git add .

git commit -m "feat: 实物阶段 - 接入 UM982 + Mid-360 + 真实底盘"

git push -u origin hardware

6.3 日常工作流

bash
# 切换到仿真分支

git checkout simulation

# 切换到实物分支

git checkout hardware

# 查看两个分支的差异

git diff simulation hardware

# 把仿真分支的某个提交同步到实物分支

git checkout hardware

git cherry-pick <commit-hash>

7. .gitignore

避免提交编译产物和 Python 缓存:

# ROS2 编译产物(不在包目录里,但以防万一)

*.pyc

__pycache__/

*.egg-info/

# 如果整个 nav_ws 都在 git 里,忽略 build/install/log

build/

install/

log/

建议:只把 src/campus_nav/ 目录初始化为 git 仓库,不要把整个 nav_ws 都放进去,build/ 和 install/ 目录很大且完全可以重新编译生成。

最近更新